Toccata
Home
Archive
Tutorials
About
RSS
[" Welcome to another exciting episode of \"Tutorials for Toccata\"! In this one, we're going to see some of Toccata's superpower in action. The path to get there is a winding one, but pretty short. Off to the right, you'll see the static view of a weather app. I lifted this wholesale from Learn ClojureScript (https://www.learn-clojurescript.com) by Andrew Meredith. We'll be using Toccata to add some life to that in a bit. But first, most programming languages have a core data structure that lets you store key/value pairs and retrieve the value given a key. It goes by various names; dictionary, hash map, or sometimes just plain map (not to be confused with the 'map' function in Toccata's core). In Toccata, we call it a hash map and it's type name is HashMap. Go ahead and define one using the following expression (def m {\"a\" 33 \"b\" 12 \"c\" \"a string instead\"}) In this case, the keys are all strings, but they can be almost anything that can be hashed (don't worry about what that means, just know you'll get an error if you try to use a value that can't be hashed.) and the values can be any type of value. In the TicTacToe tutorial, we saw the 'get' function being used to look up values in a vector given an index. The same function is implemented for HashMap's. So try (get m \"a\") (get m \"c\") (get m 71) " " The HashMap will make it's appearance late, but now we turn to the main purpose of this tutorial. Type in the following expression. (print-err \"howdy ho\") 'print-err' is a function that causes a message to printed. It's usually used when something goes wrong, hence the '-err' part of the name. But we need it for demo purposes. Now do (wasm/wait 1000) You'll see that this returns a Continuation value, but nothing much else happens. Think of a Continuation as a function that is waiting to be told what to do with the value it would ordinarily return. So let's call it and give it something to do with that value. ((wasm/wait 1000) (fn [x] (print-err \"done waiting\" x))) And it should've printed '
' immediately and then 1 second later printed the 'done waiting' message. " " Remember when I told you about Toccata's superpower and described the 'map' and 'flat-map' functions? Well there are implementations of both of those for Continuation values. Type this in (def pause (map (wasm/wait 1000) (fn [_] (print-err \"done-waiting\")))) The underscore '_' is an idiom in Toccata we use when we don't care what the value is but need a placeholder. Now, you can see what the value of 'pause' is by typing pause And you'll see it's a Continuation as expected. Let's give it something to do. (pause (fn [_] (print-err \"completely done\"))) and (pause (fn [_] (print-err \"Are you done yet?\"))) We can reuse a continuation as many times as we want. " " To make things a little easier, the WASM module includes the 'async' function. (wasm/async pause) It gives a Continuation a function that throws away the result. To round this section off, let's look at how 'flat-map' works. (wasm/async (flat-map pause (fn [_] pause))) You should see that it printed 'done waiting' twice after a second pause before each. And now, the final piece. (wasm/async (for [_ pause _ pause] (print-err \"finally-done\"))) And now we see Toccata's superpower. The same 'for' syntax is used to work with Vectors, boolean values as Maybe's, and computations that schedule things to happen in the future. " " A little terminology is in order. When you type something in the expression box and get a response, that's a synchronous computation. When you schedule something to happen some time in the future that's an asynchronous computation. Usually shortened to 'async'. The reason async is important is because the browser is only designed to follow one set of instructions at a time. If any instruction takes too long, the browser appears to hang, or performance is really bad. But the browser does allow us to tell it to do things \"in the background\" and then call a function we provide when it's done. Meanwhile, it's free to continue interacting with the user. This is really hard to do in JavaScript. So much so they added some features to make it easier. But in Toccata, you just write async code that looks like any other and it just works. The secret is having more than just 'wait' to build these programs out of. Now let's look at the next thing and we'll be ready to really leverage Toccata for coding in the browser. " " What we need to really do async programming is lots of functions like 'wait' that do things asynchronously that we can string together. And a hugely important one is making HTTP calls to websites. This is where that app to the right comes in. To make this work, you're going to need to get your own API Key from 'https://openweathermap.org'. You'll need to go there, sign up and then see what your API Key they create for you is. Got it? Great. API stand for Application Programming Interface. A fancy name for a website a program can talk to. And many sites require you to have an account to access their API. Once you have that, do (def api-key \"
\") " " Now we have to build a string that will be our request to the api. If you're familiar with making queries to web API's this needs no explanation (defn build-request [postal-code] (str \"http://api.openweathermap.org/data/2.5/forecast?q=\" postal-code \"&units=imperial\" \"&appid=\" api-key)) And finally, we can make the request when we're given a postal code. (defn request-forecast [postal] (dom/http-request \"GET\" (build-request postal))) Except this returns a Continuation. So it needs to know what to do with the response (wasm/async (map (request-forecast \"74163\") (fn [resp] (print-err \"response\" resp)))) What you see printed may at first glance look like the HashMap syntax I started this tutorial with, But look closely and you'll see it isn't. There are ':'s between the keys and values, for instance. This form of data is called JSON. " " Fortunately, JSON is extremely common in the web programming world and there are tons of libraries to convert the reponse text to data we can use. Toccata is no exception. To convert the response to our request to a HashMap, we call the 'parse' function in the JSON namespace. (wasm/async (map (request-forecast \"74163\") (fn [resp] (print-err \"response\" (json/parse resp))))) Since it's possible for us to give 'parse' a string that it can't handle, it returns a Maybe value with the HashMap value inside it. " " We can finally write the function to get the forecast and update the view. Go ahead and type this in (defn update-forecast [] (for [temp-today (dom/get-element-by-id \"temp-today\") temp-tomorrow (dom/get-element-by-id \"temp-tomorrow\") postal-code (map (dom/get-element-by-id \"postal-code\") dom/inner-html)] (wasm/async (map (request-forecast postal-code) (fn [json-str] (map (json/parse json-str) (fn [resp] (map (get-in resp [\"list\" 0 \"main\" \"temp\"]) (fn [today] (dom/inner-html temp-today today))) (map (get-in resp [\"list\" 24 \"main\" \"temp\"]) (fn [tomorrow] (dom/inner-html temp-tomorrow tomorrow)))))))))) That's a lot of lines, but it's not too complicated. The outer 'for' is just getting the places in the view to put the temps and retrieving the postal code from the view. Then we build and run an async computation that requests the forecast for that postal code. When we get that JSON string back, we parse it to get the response. And here's something new. The response is a HashMap, which has a key 'list' whose value is a Vector. And the elements of that Vector are HashMaps of hourly information. And so forth. The 'get-in' function lets you get values from deeply nested structures easily. It returns a Maybe value. So we're getting the 'temp' from this hour's forecast and from 24 hours in the future and putting them in their proper places. " " The final piece is to connect the event handler for the 'Go' button (map (dom/get-element-by-id \"go-button\") (fn [node] (wasm/handle-event node \"click\" update-forecast))) And you should be able to enter a postal code, click on 'Go' and see it update. But wait, that's not all. " " Let's say, you wanted to show the temps for 3 different places with a 1 second delay between them. Type this in (defn curr-temp [postal-code] (map (request-forecase postal-code) (fn [json-str] (for [resp (json/parse json-str) curr-temp (get-in resp [\"list\" 0 \"main\" \"temp\"])] (print-err \"Temp for\" postal-code \"is\" curr-temp))))) And then (for [_ (curr-temp \"38415\") _ (wait 1000) _ (curr-temp \"83639\") _ (wait 1000) _(curr-temp \"48192\")] '_) " ]
Prev Step
Next Step
WhichWeather
0
Today
0
Tomorrow
Enter your postal code
Go